﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Gmantis.Controls
{
    [TemplateVisualState(Name = "Valid", GroupName = "ValidationStatesStates"), 
    TemplateVisualState(Name = "Watermarked", GroupName = "WatermarkStates"), EditorBrowsable(EditorBrowsableState.Never), 
    TemplatePart(Name = "ContentElement", Type = typeof(System.Windows.Controls.ScrollViewer)), TemplatePart(Name = "Watermark", Type = typeof(ContentControl)), 
    TemplatePart(Name = "RootElement", Type = typeof(FrameworkElement)), TemplateVisualState(Name = "Unwatermarked", GroupName = "WatermarkStates"), 
    TemplateVisualState(Name = "InvalidUnfocused", GroupName = "ValidationStatesStates"), StyleTypedProperty(Property = "ValidationDecoratorStyle", StyleTargetType = typeof(ValidationDecorator)), 
    TemplatePart(Name = "FocusVisual", Type = typeof(FrameworkElement)), TemplateVisualState(Name = "InvalidFocused", GroupName = "ValidationStatesStates")]
    public class TextBoxBase : TextBox, INotifyPropertyChanged
    {
        // Fields
        internal Storyboard _currentState;
        internal System.Windows.Controls.ScrollViewer _elementContentElement;
        internal FrameworkElement _elementFocusVisual;
        internal FrameworkElement _elementRootElement;
        internal ContentControl _elementWatermark;
        private Flags _flags;
        private bool? _isTabStop;
        private string _prevText;
        public static readonly DependencyProperty GTextProperty = DependencyProperty.Register("GText", typeof(string), typeof(TextBoxBase), new PropertyMetadata("", new PropertyChangedCallback(TextBoxBase.OnTextChanged)));
        internal const string ContentElementElementName = "ContentElement";
        public static readonly DependencyProperty CornerRadiusProperty;
        public static readonly DependencyProperty DisabledCuesVisibilityProperty;
        public static readonly DependencyProperty FocusBrushProperty;
        public static readonly DependencyProperty FocusCuesVisibilityProperty;
        internal const string FocusVisualElementName = "FocusVisual";
        private static readonly DependencyProperty InternalTextProperty;
        public static readonly DependencyProperty IsFocusedProperty;
        public static readonly DependencyProperty MouseOverBrushProperty;
        internal const string RootElementElementName = "RootElement";
        public static readonly DependencyProperty SelectOnFocusProperty;
        public static readonly DependencyProperty ValidationDecoratorStyleProperty;
        internal const string WatermarkElementName = "Watermark";
        public static readonly DependencyProperty WatermarkProperty;

        // Events
        public event KeyEventHandler KeyDown;

        public event PropertyChangedEventHandler PropertyChanged;

        internal event EventHandler TextTranslation;

        // Methods
        static TextBoxBase()
        {
            InternalTextProperty = DependencyProperty.Register("InternalText", typeof(string), typeof(TextBoxBase), new PropertyMetadata("", (d, a) => (d as TextBoxBase).OnTextChanged(a)));
            WatermarkProperty = DependencyProperty.Register("Watermark", typeof(object), typeof(TextBoxBase), new PropertyMetadata(new PropertyChangedCallback(TextBoxBase.OnWatermarkPropertyChanged)));
            IsFocusedProperty = DependencyProperty.Register("IsFocused", typeof(bool), typeof(TextBoxBase), new PropertyMetadata(new PropertyChangedCallback(TextBoxBase.OnIsFocusedPropertyChanged)));
            CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(TextBoxBase), null);
            SelectOnFocusProperty = DependencyProperty.Register("SelectOnFocus", typeof(SelectOnFocusCondition), typeof(TextBoxBase), new PropertyMetadata(SelectOnFocusCondition.OnDefaultSelection));
            FocusCuesVisibilityProperty = DependencyProperty.Register("FocusCuesVisibility", typeof(Visibility), typeof(TextBoxBase), new PropertyMetadata(Visibility.Visible));
            DisabledCuesVisibilityProperty = DependencyProperty.Register("DisabledCuesVisibility", typeof(Visibility), typeof(TextBoxBase), new PropertyMetadata(Visibility.Visible));
            ValidationDecoratorStyleProperty = DependencyProperty.Register("ValidationDecoratorStyle", typeof(Style), typeof(TextBoxBase), null);
            MouseOverBrushProperty = DependencyProperty.Register("MouseOverBrush", typeof(Brush), typeof(TextBoxBase), null);
            FocusBrushProperty = DependencyProperty.Register("FocusBrush", typeof(Brush), typeof(TextBoxBase), null);
        }

        public TextBoxBase()
        {
            RoutedEventHandler handler = null;
            base.DefaultStyleKey = typeof(TextBoxBase);
            if (handler == null)
            {
                handler = (param0, param1) => this.ChangeVisualStateWatermark(false);
            }
            base.Loaded += handler;
            base.GotFocus += new RoutedEventHandler(this.Control_GotFocus);
            base.LostFocus += new RoutedEventHandler(this.Control_LostFocus);
            this.SetCustomDefaultValues();
        }

        private void Control_GotFocus(object sender, RoutedEventArgs e)
        {
            this.IsFocused = true;
        }

        private void Control_LostFocus(object sender, RoutedEventArgs e)
        {
            this.IsFocused = false;
        }

        private void MaskedTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (this._prevText != base.Text)
            {
                this._prevText = base.Text;
                this.ProcessTextChangedEvent(e);
            }
        }

        private void MaskedTextBox_TextInputStart(object sender, TextCompositionEventArgs e)
        {
            this.SetFlag(Flags.ImeStarted, true);
            this.SetFlag(Flags.ImeReplaceStarted, !string.IsNullOrEmpty(e.TextComposition.CompositionText));
        }

        private void MaskedTextBox_TextInputUpdate(object sender, TextCompositionEventArgs e)
        {
            this.SetFlag(Flags.ImeUpdated, !string.IsNullOrEmpty(e.TextComposition.CompositionText));
        }

        protected void ChangeVisualStateWatermark(bool useTransitions)
        {
            if (this.IsWatermarked)
            {
                VisualStateHelper.GoToState(this, "Watermarked", useTransitions);
            }
            if (!this.IsWatermarked)
            {
                VisualStateHelper.GoToState(this, "Unwatermarked", useTransitions);
            }
        }

        internal virtual void DoSelectOnFocus()
        {
            if ((this.SelectOnFocus == SelectOnFocusCondition.Always) || (((this.SelectOnFocus == SelectOnFocusCondition.OnDefaultSelection) && (base.SelectionStart == 0)) && (base.SelectionLength == 0)))
            {
                try
                {
                    base.Select(0, base.Text.Length);
                }
                catch (Exception)
                {
                }
            }
        }

        private bool GetFlag(Flags flag)
        {
            return ((this._flags & flag) == flag);
        }

        private T GetTemplateChild<T>(string childName, bool required, ref string errors) where T : class
        {
            DependencyObject templateChild = base.GetTemplateChild(childName);
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "template part", childName, templateChild, required, ref errors);
            return (templateChild as T);
        }

        private static Storyboard GetTemplateChildResource(FrameworkElement root, string resourceName, bool required, ref string errors)
        {
            return GetTemplateChildResource<Storyboard>(root, resourceName, required, ref errors);
        }

        private static T GetTemplateChildResource<T>(FrameworkElement root, string resourceName, bool required, ref string errors) where T : class
        {
            object child = root.Resources[resourceName];
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "resource", resourceName, child, required, ref errors);
            return (child as T);
        }

        private void OnAfterApplyTemplate()
        {
            this.OnWatermarkChanged(null);
            base.SetBinding(InternalTextProperty, new Binding().From<TextBoxBase>(this, x => x.Text));
            this.UpdateIsWatermarked();
        }

        public override void OnApplyTemplate()
        {
            string errors = string.Empty;
            base.OnApplyTemplate();
            this._elementWatermark = this.GetTemplateChild<ContentControl>("Watermark", false, ref errors);
            this._elementContentElement = this.GetTemplateChild<System.Windows.Controls.ScrollViewer>("ContentElement", true, ref errors);
            this._elementFocusVisual = this.GetTemplateChild<FrameworkElement>("FocusVisual", false, ref errors);
            this._elementRootElement = this.GetTemplateChild<FrameworkElement>("RootElement", true, ref errors);
            if (!string.IsNullOrEmpty(errors))
            {
                if ((base.Template != null) && !PlatformIndependent.IsInDesignMode(this))
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Template cannot be applied to TextBoxBase.\nDetails: {0}", new object[] { errors }));
                }
            }
            else
            {
                this.ChangeVisualStateWatermark(false);
                this.OnAfterApplyTemplate();
            }
        }

        private void OnIsEnabledChanged(bool oldValue)
        {
            if (base.IsEnabled)
            {
                if (this._isTabStop.HasValue)
                {
                    base.IsTabStop = this._isTabStop.Value;
                }
            }
            else
            {
                this._isTabStop = new bool?(base.IsTabStop);
                base.IsTabStop = false;
            }
        }

        protected virtual void OnIsFocusedChanged(bool oldValue)
        {
            if (this.IsFocused && base.IsEnabled)
            {
                this.DoSelectOnFocus();
            }
            this.UpdateIsWatermarked();
        }

        private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBoxBase base2 = d as TextBoxBase;
            bool oldValue = (bool)e.OldValue;
            base2.OnIsFocusedChanged(oldValue);
        }

        protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
        {
            if (this.KeyDown != null)
            {
                this.KeyDown(this, e);
            }
            if (!e.Handled)
            {
                base.OnKeyDown(e);
            }
        }

        protected virtual void OnTextChanged(DependencyPropertyChangedEventArgs e)
        {
            base.SetValue(GTextProperty, e.NewValue);
            this.UpdateIsWatermarked();
        }

        private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBoxBase base2 = d as TextBoxBase;
            string newValue = e.NewValue as string;
            if (base2.Text != newValue)
            {
                base2.Text = newValue ?? "";
            }
        }

        protected override void OnTextInput(TextCompositionEventArgs e)
        {
            base.OnTextInput(e);
            this.ProcessTextChangedEvent(null);
        }

        internal virtual void OnTextTranslation()
        {
            if (this.TextTranslation != null)
            {
                this.TextTranslation(this, EventArgs.Empty);
            }
        }

        internal virtual void OnWatermarkChanged(object oldValue)
        {
            if (this._elementWatermark != null)
            {
                Control watermark = this.Watermark as Control;
                if (watermark != null)
                {
                    watermark.IsTabStop = false;
                    watermark.IsHitTestVisible = false;
                }
            }
        }

        private static void OnWatermarkPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBoxBase base2 = d as TextBoxBase;
            object oldValue = e.OldValue;
            base2.OnWatermarkChanged(oldValue);
        }

        private void ProcessTextChangedEvent(TextChangedEventArgs e)
        {
            if (this.GetFlag(Flags.ImeReplaceStarted))
            {
                this.SetFlag(Flags.ImeReplaceStarted, false);
            }
            else if (this.GetFlag(Flags.ImeUpdated))
            {
                this.SetFlag(Flags.ImeUpdated, false);
            }
            else if (this.GetFlag(Flags.ImeStarted))
            {
                this.SetFlag(Flags.ImeStarted, false);
                this.OnTextTranslation();
            }
            else
            {
                this.OnTextTranslation();
            }
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private void SetCustomDefaultValues()
        {
            base.IsEnabled = true;
            this.UpdateIsWatermarked();
            base.TextChanged += (s, e) => base.SetValue(GTextProperty, base.Text);
            base.AddHandler(UIElement.TextInputStartEvent, new TextCompositionEventHandler(this.MaskedTextBox_TextInputStart), true);
            base.AddHandler(UIElement.TextInputUpdateEvent, new TextCompositionEventHandler(this.MaskedTextBox_TextInputUpdate), true);
            base.TextChanged += new TextChangedEventHandler(this.MaskedTextBox_TextChanged);
        }

        private void SetFlag(Flags flag, bool on)
        {
            if (on)
            {
                this._flags |= flag;
            }
            else
            {
                this._flags &= ~flag;
            }
        }

        protected void UpdateIsWatermarked()
        {
            this.ChangeVisualStateWatermark(true);
            this.RaisePropertyChanged("IsWatermarked");
        }

        // Properties
        public string GText
        {
            get { return (string)GetValue(GTextProperty); }
            set { SetValue(GTextProperty, value); }
        }

        public CornerRadius CornerRadius
        {
            get { return (CornerRadius)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }

        public Visibility DisabledCuesVisibility
        {
            get { return (Visibility)GetValue(DisabledCuesVisibilityProperty); }
            set { SetValue(DisabledCuesVisibilityProperty, value); }
        }

        public Brush FocusBrush
        {
            get { return (Brush)GetValue(FocusBrushProperty); }
            set { SetValue(FocusBrushProperty, value); }
        }

        public Visibility FocusCuesVisibility
        {
            get { return (Visibility)GetValue(FocusCuesVisibilityProperty);}
            set { SetValue(FocusCuesVisibilityProperty, value); }
        }

        public bool IsFocused
        {
            get { return (bool)GetValue(IsFocusedProperty); }
            set { SetValue(IsFocusedProperty, value); }
        }

        public virtual bool IsWatermarked
        {
            get
            {
                if (base.IsReadOnly)
                {
                    return string.IsNullOrEmpty(base.Text);
                }
                return (!this.IsFocused && string.IsNullOrEmpty(base.Text));
            }
        }

        public Brush MouseOverBrush
        {
            get { return (Brush)GetValue(MouseOverBrushProperty); }
            set { SetValue(MouseOverBrushProperty, value); }
        }

        public SelectOnFocusCondition SelectOnFocus
        {
            get { return (SelectOnFocusCondition)GetValue(SelectOnFocusProperty); }
            set { SetValue(SelectOnFocusProperty, value); }
        }

        public Style ValidationDecoratorStyle
        {
            get { return (Style)GetValue(ValidationDecoratorStyleProperty); }
            set { SetValue(ValidationDecoratorStyleProperty, value); }
        }

        public object Watermark
        {
            get { return GetValue(WatermarkProperty); }
            set { SetValue(WatermarkProperty, value); }
        }

        private enum Flags
        {
            IgnorePromptChange = 0x20,
            ImeReplaceStarted = 0x10,
            ImeStarted = 4,
            ImeUpdated = 8,
            IsLoaded = 0x40,
            LASTCOMMON = 0x20
        }
    }    

    public enum SelectOnFocusCondition
    {
        Never,
        Always,
        OnDefaultSelection
    }
}
